import {
  Body,
  Controller,
  Get,
  Inject,
  Injectable,
  Module,
  Param,
  Post,
  Request,
} from "@nestjs/common";
import { Prisma, PrismaClient } from "@prisma/client";
import { WebResult } from "src/common/web.result";
import { ArticleCommentDTO } from "./dto/comment.dto";
import { FastifyRequest } from "fastify";
import { InformationService } from "src/common/information.module";
import { Transporter } from "nodemailer";
import configuration from "src/common/configuration";
import { UserService } from "src/user/user.service";
import { UserModule } from "src/user/user.module";
import { UserTokenService } from "src/user/token.service";
import { ArticleModule } from "./article.module";
import { ArticleService } from "./article.service";

const defaultPageSize = 10;
@Injectable()
export class ArticleCommentService {
  constructor(
    private readonly prisma: PrismaClient,
    @Inject("Transporter") private readonly emailTrasnporter: Transporter,
    private readonly userService: UserService,
    private readonly articleService: ArticleService,
  ) {}

  // 评论相关
  validEmail(email: string) {
    if (!email) {
      return false;
    }
    return /[^@]{1,}@[^\.]{1,}\..{1,}/i.test(email);
  }

  async getArticleCommentCount(
    commentState: "all" | "reviewing" | "pass" | "reject",
    articleID?: number,
  ) {
    if (isNaN(articleID)) {
      articleID = undefined;
    }
    if (commentState === "all") {
      return await this.prisma.article_comment.count();
    } else if (commentState === "reviewing" || commentState === "reject") {
      return await this.prisma.article_comment_review.count({
        where: {
          state: commentState,
        },
      });
    } else if (commentState === "pass") {
      const sql = `
        select count(*) as count from (
          select c.id, ${articleID ? " c.article_id, " : ""} r.comment_id, r.state
          from article_comment c
          left join article_comment_review r
          on c.id = r.comment_id
          where
          (r.state is null or r.state = 'pass')
          ${articleID ? " and c.article_id = " + articleID : ""}
        )
      `;
      const rows: any = await this.prisma.$queryRaw(Prisma.sql([sql]));
      if (rows && rows[0]) {
        return rows[0].count;
      }
      return 0;
    } else {
      return 0;
    }
  }

  getArticleComments(
    commentState: "all" | "reviewing" | "pass" | "reject",
    articleID?: number,
  ) {
    if (isNaN(articleID)) {
      articleID = undefined;
    }
    return this.getArticleCommentsByPage(commentState, "all", 0, articleID);
  }

  async getArticleCommentsByPage(
    commentState: "all" | "reviewing" | "pass" | "reject",
    page: number | "all",
    pageSize: number = defaultPageSize,
    articleID?: number,
  ): Promise<Array<any>> {
    if (isNaN(articleID)) {
      articleID = undefined;
    }
    if (typeof pageSize !== "number" || isNaN(pageSize)) {
      pageSize = defaultPageSize;
    }
    const skip = page === "all" ? undefined : (page - 1) * pageSize;
    const limitSQL = page === "all" ? "" : `limit ${skip}, ${pageSize}`;
    if (commentState === "all") {
      const take = page === "all" ? undefined : pageSize;
      return await this.prisma.article_comment.findMany({
        skip,
        take,
        orderBy: {
          time: "desc",
        },
        include: {
          article: {
            select: {
              title: true,
            },
          },
        },
        where: articleID ? { article_id: articleID } : undefined,
      });
    } else if (commentState === "reviewing" || commentState === "reject") {
      const sql = `
        select a.id as article_id, a.title as article_title, c.*, r.comment_id, r.state
        from article a, article_comment c
        on a.id = c.article_id
        left join article_comment_review r
        on c.id = r.comment_id
        where
        a.id = c.article_id and r.state = '${commentState}'
        ${articleID ? "and c.article_id = " + articleID : ""}
        order by c.time desc
        ${limitSQL}
      `;
      return await this.prisma.$queryRaw(Prisma.sql([sql]));
    } else if (commentState === "pass") {
      const sql = `
        select a.id as article_id, a.title as article_title, c.*, r.comment_id, r.state
        from article a
        join article_comment c
        on a.id = c.article_id
        left join article_comment_review r
        on c.id = r.comment_id
        where
        (r.state is null or r.state = 'pass')
        ${articleID ? "and c.article_id = " + articleID : ""}
        order by c.time desc
        ${limitSQL}
      `;
      return await this.prisma.$queryRaw(Prisma.sql([sql]));
    } else {
      return [];
    }
  }

  // 评论邮件通知相关

  /**
   * 通知站长
   */
  async notifyMaster(comment: {
    article_id: number;
    commenter: string;
    commenter_email: string;
    content: string;
  }) {
    if (this.userService.getMasterEmails().length === 0) {
      console.log("站长邮箱地址未配置");
      return;
    }
    for (const email of this.userService.getMasterEmails()) {
      if (!this.validEmail(email)) {
        continue;
      }
      this.emailTrasnporter.sendMail({
        from: `"${configuration.website.name}" <${configuration.email.user}>`,
        to: email,
        subject: `
          在${configuration.website.name}上的文章${comment.article_id}（${await this.articleService.getArticleTitleByID(comment.article_id)}）有了新的评论
        `.trim(),
        html: `
          <div>
            <h4>${comment.commenter}&lt;${comment.commenter_email}&gt;评论：</h4>
            <div>
              ${await this.articleService.markdown2HTML(comment.content, true)}
            </div>
          </div>
        `,
      });
    }
  }

  /**
   * 通知站长
   */
  async notifyMasterByCommentID(commentID: number) {
    const comment = await this.prisma.article_comment.findUnique({
      select: {
        article_id: true,
        commenter: true,
        commenter_email: true,
        content: true,
      },
      where: {
        id: commentID,
      },
    });
    if (comment) {
      await this.notifyMaster(comment);
    }
  }

  /**
   * 通知发布者
   */
  async notifyPublisher(comment: {
    article_id: number;
    commenter: string;
    content: string;
  }) {
    const publisherID = await this.articleService.getArticlePublisherIDByID(
      comment.article_id,
    );
    const publisherEmail = await this.userService.getUserEmailByID(publisherID);
    if (!publisherID || !publisherEmail || !this.validEmail(publisherEmail)) {
      return;
    }
    this.emailTrasnporter.sendMail({
      from: `"${configuration.website.name}" <${configuration.email.user}>`,
      to: publisherEmail,
      subject: `
        您在“${configuration.website.name}”上发布的文章“${await this.articleService.getArticleTitleByID(comment.article_id)}”收到了新的评论
      `.trim(),
      html: `
        <div>
          <h4>“${comment.commenter}”评论：</h4>
          <div>
            ${await this.articleService.markdown2HTML(comment.content, true)}
          </div>
        </div>
      `,
    });
  }
  /**
   * 通知发布者
   */
  async notifyPublisherByCommentID(commentID: number) {
    const comment = await this.prisma.article_comment.findUnique({
      select: {
        article_id: true,
        commenter: true,
        content: true,
      },
      where: {
        id: commentID,
      },
    });
    if (comment) {
      await this.notifyPublisher(comment);
    }
  }

  /**
   * 通知被回复的人
   */
  async notifyParentCommenter(
    parentComment: {
      commenter: string;
      commenter_email: string;
      content: string;
    },
    comment: {
      article_id: number;
      commenter: string;
      content: string;
    },
  ) {
    if (!this.validEmail(parentComment.commenter_email)) {
      return;
    }
    this.emailTrasnporter.sendMail({
      from: `"${configuration.website.name}" <${configuration.email.user}>`, // sender address
      to: parentComment.commenter_email, // list of receivers
      subject: `${configuration.website.name}——评论提醒`, // Subject line
      html: `
        <h3>
          您在
          “<a href="${configuration.website.url}">${configuration.website.name}</a>”
          上的评论有了新的回复
        </h3>
        <div>
          <h4>“${parentComment.commenter}”，你曾评论：</h4>
          <div>
            ${await this.articleService.markdown2HTML(parentComment.content, true)}
          </div>
        </div>
        <div>
          <h4>“${comment.commenter}”回复：</h4>
          <div>
            ${await this.articleService.markdown2HTML(comment.content, true)}
          </div>
        </div>
        <div>
          您可以点击
          <a href="${configuration.website.url}/pages/article.html?id=${comment.article_id}">查看回复的完整内容</a>
          ，
          欢迎再次光临
          <a href="${configuration.website.url}">${configuration.website.name}</a>
        </div>
      `, // html body
    });
  }

  /**
   * 通知被回复的人
   */
  async notifyParentCommenterByCommentID(commentID: number) {
    const comment = await this.prisma.article_comment.findUnique({
      select: {
        article_id: true,
        parent_id: true,
        commenter: true,
        commenter_email: true,
        content: true,
      },
      where: {
        id: commentID,
      },
    });
    if (!comment || !comment.parent_id) {
      return;
    }
    const parentComment = await this.prisma.article_comment.findUnique({
      select: {
        commenter: true,
        commenter_email: true,
        content: true,
      },
      where: {
        id: comment.parent_id,
      },
    });
    if (!parentComment || !parentComment.commenter_email) {
      return;
    }
    this.notifyParentCommenter(parentComment, comment);
  }

  // 点赞功能
  countCommentLike({
    commentID,
    type,
  }: {
    commentID: number;
    type: "like" | "unlike";
  }) {
    return this.prisma.article_comment_like.count({
      where: {
        comment_id: commentID,
        type,
      },
    });
  }

  async likeComment({
    commentID,
    ip,
    type,
  }: {
    commentID: number;
    ip: string;
    type: "like" | "unlike";
  }) {
    // 查询记录
    const likes = await this.prisma.article_comment_like.findMany({
      where: {
        comment_id: commentID,
        ip,
      },
      take: 1,
    });
    if (!likes || !likes[0]) {
      try {
        await this.prisma.article_comment_like.create({
          data: {
            comment_id: commentID,
            ip,
            type,
          },
        });
        return true;
      } catch (e) {
        console.error(e);
        return false;
      }
    }

    // 记录类型（record.type）与操作类型（参数type）相同
    if (likes[0].type === type) {
      try {
        await this.prisma.article_comment_like.deleteMany({
          where: {
            comment_id: commentID,
            ip,
            type,
          },
        });
        return true;
      } catch (e) {
        console.error(e);
        return false;
      }
    }

    // 记录类型（record.type）与操作类型（参数type）不同
    try {
      await this.prisma.article_comment_like.updateMany({
        data: {
          type: type === "like" ? "unlike" : "like",
        },
        where: {
          comment_id: commentID,
          ip,
        },
      });
      return true;
    } catch (e) {
      console.error(e);
      return false;
    }
  }
}

@Controller("/article/comment")
class ArticleCommentController {
  constructor(
    private readonly prisma: PrismaClient,
    @Inject("Transporter") private readonly emailTrasnporter: Transporter,
    private readonly informationService: InformationService,
    private readonly articleCommentService: ArticleCommentService,
    private readonly userService: UserService,
    private readonly userTokenService: UserTokenService,
    private readonly articleService: ArticleService,
  ) {}

  @Get("/like/count/:id")
  async getLikeCountByCommentID(@Param("id") id: string) {
    const commentID = parseInt(id);
    if (isNaN(commentID)) {
      return WebResult.failure(undefined, "id is not a number");
    }
    const count = await this.articleCommentService.countCommentLike({
      commentID,
      type: "like",
    });
    return WebResult.success(count, "获取成功");
  }
  @Get("/unlike/count/:id")
  async getUnlikeCountByCommentID(@Param("id") id: string) {
    const commentID = parseInt(id);
    if (isNaN(commentID)) {
      return WebResult.failure(undefined, "id is not a number");
    }
    const count = await this.articleCommentService.countCommentLike({
      commentID,
      type: "unlike",
    });
    return WebResult.success(count, "获取成功");
  }
  @Post("/like/:id")
  async like(@Request() request: FastifyRequest, @Param("id") id: string) {
    const commentID = parseInt(id);
    if (isNaN(commentID)) {
      return WebResult.failure(undefined, "id is not a number");
    }
    const ip = this.informationService.getIP(request);
    if (!ip) {
      return WebResult.failure(undefined, "点赞失败");
    }
    if (
      this.articleCommentService.likeComment({ commentID, ip, type: "like" })
    ) {
      return WebResult.success(undefined, "操作成功");
    }
    return WebResult.failure(undefined, "操作失败，内部错误");
  }
  @Post("/unlike/:id")
  async unlike(@Request() request: FastifyRequest, @Param("id") id: string) {
    const commentID = parseInt(id);
    if (isNaN(commentID)) {
      return WebResult.failure(undefined, "id is not a number");
    }
    const ip = this.informationService.getIP(request);
    if (!ip) {
      return WebResult.failure(undefined, "点踩失败");
    }
    if (
      this.articleCommentService.likeComment({ commentID, ip, type: "unlike" })
    ) {
      return WebResult.success(undefined, "操作成功");
    }
    return WebResult.failure(undefined, "操作失败，内部错误");
  }

  @Post("/")
  async commentByArticleID(
    @Request() request: FastifyRequest,
    @Body() dto: ArticleCommentDTO,
  ) {
    const ip = this.informationService.getIP(request);
    if (!ip) {
      return WebResult.failure(undefined, "评论失败");
    }

    if (
      this.userService.isMaster(dto.commenter) ||
      this.userService.getMasterEmails().includes(dto.commenter_email)
    ) {
      const username = this.userTokenService.getUsernameFromRequest(request);
      if (!username || this.userService.getMasterUsername() !== username) {
        return WebResult.failure(undefined, "以站长身份发言需登录");
      }
    }

    let articleID = dto.article_id;
    let topCommentID: number;
    let parentComment: {
      article_id: number;
      top_comment_id: number;
      commenter: string;
      commenter_email: string | undefined | null;
      content: string;
    };
    // 回复评论
    if (dto.parent_id) {
      parentComment = await this.prisma.article_comment.findUnique({
        select: {
          article_id: true,
          top_comment_id: true,
          commenter: true,
          commenter_email: true,
          content: true,
        },
        where: {
          id: dto.parent_id,
        },
      });
      if (!parentComment) {
        return WebResult.failure(undefined, "所回复的评论不存在");
      }
      if (!parentComment.top_comment_id) {
        topCommentID = dto.parent_id;
      } else {
        topCommentID = parentComment.top_comment_id;
      }
      articleID = parentComment.article_id;
    } else if (dto.article_id) {
      const article = await this.prisma.article.findUnique({
        select: {
          id: true,
        },
        where: {
          id: dto.article_id,
        },
      });
      if (!article) {
        return WebResult.failure(undefined, "所评论的文章不存在");
      }
    }

    const area = this.informationService.getRegion(ip);
    const { os, browser } = this.informationService.getUAInformation(request);
    const comment = await this.prisma.article_comment.create({
      data: {
        article_id: articleID,
        top_comment_id: topCommentID,
        parent_id: dto.parent_id,
        commenter: dto.commenter,
        commenter_email: this.articleCommentService.validEmail(
          dto.commenter_email,
        )
          ? dto.commenter_email
          : null,
        commenter_site: dto.commenter_site,
        content: dto.content,
        ip,
        area,
        os,
        browser,
      },
    });
    if (!comment) {
      return WebResult.failure(undefined, "评论失败，内部错误");
    }

    // 开启了评论审核
    // 站长评论直接过
    if (
      this.userService.isMaster(comment.commenter) ||
      this.userService.getMasterEmails().includes(comment.commenter_email)
    ) {
      return WebResult.success(undefined, "评论成功");
    }
    // 新增 待审核评论
    else if (configuration.article.enableCommentReview) {
      const reviewRecord = await this.prisma.article_comment_review.create({
        data: {
          comment_id: comment.id,
        },
      });
      if (!reviewRecord) {
        this.prisma.article_comment
          .delete({
            where: {
              id: comment.id,
            },
          })
          .then();
        return WebResult.failure(undefined, "评论失败，内部错误");
      }
      return WebResult.success(undefined, "评论成功，审核通过后评论将展示");
    }

    // 未开启审核功能，进行邮件通知
    if (configuration.email.enable) {
      // 通知站长
      // this.articleCommentService.notifyMaster(dto);
      // 通知文章发布者
      this.articleCommentService.notifyPublisher(dto);
      // 是回复，通知被回复的人
      if (parentComment && parentComment.commenter_email) {
        this.articleCommentService.notifyParentCommenter(parentComment, dto);
      }
    }
    return WebResult.success(undefined, "评论成功");
  }

  @Get("/count/:id")
  async getArticleCommentCountByArticleID(@Param("id") id: string) {
    const articleID = parseInt(id);
    if (isNaN(articleID)) {
      return WebResult.failure(undefined, "id is not a number");
    }
    const count = await this.articleCommentService.getArticleCommentCount(
      "pass",
      articleID,
    );
    return WebResult.success(count, "获取成功");
  }

  @Get("/:id")
  async getArticleCommentsByArticleID(@Param("id") id: string) {
    const articleID = parseInt(id);
    if (isNaN(articleID)) {
      return WebResult.failure(undefined, "id is not a number");
    }
    const comments = await this.articleCommentService.getArticleComments(
      "pass",
      articleID,
    );
    // 评论过滤
    comments.forEach((item: any) => {
      // 站长评论
      if (this.userService.isMaster(item.commenter)) {
        item.is_master = true;
        item.master = true;
      }
    });
    return WebResult.success(comments, "获取成功");
  }
}

@Module({
  imports: [UserModule, ArticleModule],
  controllers: [ArticleCommentController],
  providers: [ArticleCommentService],
  exports: [ArticleCommentService],
})
export class ArticleCommentModule {}
